#region License
/*
The MIT License

Copyright (c) 2008 Sky Morey

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion
using System;
using System.IO;
using System.Text;
using Native_;
namespace Capture.Utility
{
    /// <summary>
    /// Stream implementaion of Wave
    /// </summary>
    public class WaveStream : Stream, IDisposable
    {
        private Stream _parentStream;
        private long _dataIndex;
        private long _length;
        private WaveFormat _waveFormat;
        private bool _disposed = false;

        public WaveStream(string fileName)
            : this(new FileStream(fileName, FileMode.Open)) { }
        public WaveStream(Stream stream)
        {
            _parentStream = stream;
            ReadHeader();
        }
        ~WaveStream() { if (!_disposed) Dispose(false); }
        protected override void Dispose(bool disposing)
        {
            _disposed = true;
            if ((disposing) && (_parentStream != null))
                _parentStream.Close();
            base.Dispose(disposing);
        }

        public WaveFormat Format
        {
            get { return _waveFormat; }
        }

        private string ReadChunk(BinaryReader r)
        {
            var c = new byte[4];
            r.Read(c, 0, c.Length);
            return Encoding.ASCII.GetString(c);
        }

        private void ReadHeader()
        {
            var reader = new BinaryReader(_parentStream);
            if (ReadChunk(reader) != "RIFF")
                throw new Exception("Invalid file format");
            // file length minus first 8 bytes of RIFF description, we don't use it
            reader.ReadInt32();
            if (ReadChunk(reader) != "WAVE")
                throw new Exception("Invalid file format");
            if (ReadChunk(reader) != "fmt ")
                throw new Exception("Invalid file format");
            int length = reader.ReadInt32();
            // bad format chunk length
            if (length < 16)
                throw new Exception("Invalid file format");
            // initialize to any format
            _waveFormat = new WaveFormat(22050, 16, 2)
            {
                wFormatTag = reader.ReadInt16(),
                nChannels = reader.ReadInt16(),
                nSamplesPerSec = reader.ReadInt32(),
                nAvgBytesPerSec = reader.ReadInt32(),
                nBlockAlign = reader.ReadInt16(),
                wBitsPerSample = reader.ReadInt16(),
            };
            // advance in the stream to skip the wave format block 
            // minimum format size
            length -= 16;
            while (length > 0)
            {
                reader.ReadByte();
                length--;
            }
            // assume the data chunk is aligned
            while ((_parentStream.Position < _parentStream.Length) && (ReadChunk(reader) != "data")) ;
            if (_parentStream.Position >= _parentStream.Length)
                throw new Exception("Invalid file format");
            _length = reader.ReadInt32();
            _dataIndex = _parentStream.Position;
            Position = 0;
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override long Length
        {
            get { return _length; }
        }

        public override long Position
        {
            get { return _parentStream.Position - _dataIndex; }
            set { Seek(value, System.IO.SeekOrigin.Begin); }
        }

        public override void Flush() { }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Begin:
                    _parentStream.Position = offset + _dataIndex;
                    break;
                case SeekOrigin.Current:
                    _parentStream.Seek(offset, SeekOrigin.Current);
                    break;
                case SeekOrigin.End:
                    _parentStream.Position = _dataIndex + _length - offset;
                    break;
            }
            return this.Position;
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            int readLength = (int)Math.Min(count, _length - Position);
            return _parentStream.Read(buffer, offset, readLength);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }
    }
}
